-module(dmbot).
-export([start/4]).

start(Host, Port, Nick, Channel) ->
    case gen_tcp:connect(Host, Port, [{packet, line}]) of
        {ok, Socket} ->
            gen_tcp:send(Socket, "NICK " ++ Nick ++ "\r\n"),
            %USER <user> <mode> <unused> :<realname>
            gen_tcp:send(Socket, "USER " ++ Nick ++ " 0 _ :dmbot\r\n"),
            join(Socket, Channel),
            loop(state:new(Socket, world:new(Channel)));
        Error ->
            Error
    end.

% Wait for MOTD on Socket and then join Channel.
join(Socket, Channel) ->
    receive
        {tcp, Socket, [$P, $I, $N, $G | Message]} ->
            gen_tcp:send(Socket, "PONG" ++ Message);
        {tcp, Socket, Line} ->
            case lists:nth(2, string:tokens(Line, ": ")) of
                "376" -> %MOTD is over
                    gen_tcp:send(Socket, "JOIN :" ++ Channel ++ "\r\n");
                _ ->
                    io:format("~s", [Line]),
                    join(Socket, Channel)
            end
    end.

% Act on messages: handle actions.
loop(State) ->
    Socket = state:socket(State),
    receive
        {tcp, Socket, [$P, $I, $N, $G | Message]} -> % Prioritize PING first.
            gen_tcp:send(Socket, "PONG" ++ Message),
            loop(State);
        {tcp, Socket, Line} -> % Parse message lines and handle actions.
            io:format("~s", [Line]),
            case parse(string:tokens(Line, ": \r\n")) of
                {ok, Action} -> % Update state, add events to the event queue.
                    NewState = handle(Action, State),
                    loop(NewState);
                _ ->
                    loop(State)
            end
    after 1000 ->
            % Apply events with delay 0, decrease delay of the others.
            loop(tick(State))
    end.

% Send irc message Message to Target on Socket.
privmsg(Socket, Target, Message) ->
    gen_tcp:send(Socket, "PRIVMSG " ++ Target ++ " :" ++ Message ++ "\r\n").

% Parse messages.
parse([User, "PRIVMSG", _Channel, [$! | Command] | Args]) ->
    [Nick | _Mask] = string:tokens(User, "!"),
    {ok, action:new(Nick, Command, Args)};
parse(_) ->
    error.

% Handle actions, and update state.
handle(Action, State) ->
    case action:command(Action) of
        "register" -> % Register a new player, create a char.
            World = state:world(State),
            Players = world:players(World),
            NewPlayer = action:player(Action),
            NewPlayers = [char:new(NewPlayer, 100, 10, 10) | Players],
            NewWorld = world:players(World, NewPlayers),
            state:world(State, NewWorld);
        "stats" -> % Print stats for a player.
            % shouldn't use keysearch here
            case lists:keysearch(action:player(Action), 2,
                                 world:players(state:world(State))) of
                {value, Char} ->
                    privmsg(state:socket(State),
                            world:channel(state:world(State)),
                            char:stats(Char));
                _ ->
                    ok
            end,
            State;
        "attack" -> % Add an autoattack event to the event queue.
            case action:args(Action) of
                [Target | _] ->
                    World = state:world(State),
                    Events = world:events(World),
                    Player = action:player(Action),
                    NEvents = [event:new(5, true, Player, attack,
                               [Target])
                               |Events],
                    NewWorld = world:events(World, NEvents),
                    state:world(State,NewWorld);
                _ ->
                    privmsg(state:socket(State),
                            world:channel(state:world(State)),
                            action:player(Action) ++ ": gief target plz"),
                    State
            end;
        "magic" -> % Add a spell cast to the event queue.
            case action:args(Action) of
                [Spell, Target | _] ->
                    World = state:world(State),
                    Events = world:events(World),
                    Player = action:player(Action),
                    NEvents = [event:new(8, false, Player, magic,
                               [Spell, Target])
                               |Events],
                    NewWorld = world:events(World, NEvents),
                    state:world(State,NewWorld);
                _ ->
                    privmsg(state:socket(State),
                            world:channel(state:world(State)),
                            action:player(Action) ++ ": gief spell and target plz"),
                    State
            end;
        "players" -> % List active players.
            World = state:world(State),
            privmsg(state:socket(State), world:channel(World),
                    io_lib:format("~p", [world:players(World)])),
            State
    end.

% Update the delay of all events in the event queue,
% apply the events if their delay is 0.
tick(State) ->
    World = state:world(State),
    % Decrease the delay for each event in the event queue.
    TickedEvents = lists:map(fun(E) -> event:delay(E, event:delay(E) - 1) end,
                             world:events(World)),
    % Filter out events to apply.
    {ToApply, ToKeep} = lists:partition(fun(E) -> event:delay(E) < 1 end,
                                        TickedEvents),
    % Filter out events to be dropped after application.
    ToReset = lists:filter(fun(E) -> event:auto(E) end, ToApply),
    % Reset delay for kept events.
    Reset = lists:map(fun(E) -> event:reset(E) end, ToReset),
    NewEvents = ToKeep ++ Reset,
    NewWorld = world:events(World, NewEvents),
    NewState = state:world(State, NewWorld),
    % Apply the events to World.
    lists:foldl(fun (E, S) -> appl(E, World, S) end, NewState, ToApply).

% Apply Event to World
appl(Event, World, State) ->
    case event:command(Event) of
        attack ->
            privmsg(state:socket(State), world:channel(World),
                    io_lib:format("~s hits ~s.",
                                  [event:player(Event),
                                   hd(event:args(Event))])),
            State;
        magic ->
            privmsg(state:socket(State), world:channel(World),
                    io_lib:format("~s casts ~s on ~s.",
                                  [event:player(Event),
                                   hd(event:args(Event)),
                                   hd(tl(event:args(Event)))])),
            State;
        _ ->
            State
    end.
